/* 
 *	Copyright (C) 2009-2010 miaohua
 *	http://code.google.com/p/lrcshower/
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "Stdafx.h"
#include <tchar.h>
#include <cmath>
#include <cassert>
#include <string>
#include "LrcWinShower.h"
#include "LrcAnalyzer.h"
using namespace std;

#define Text2Screen(Buffer, Option, Color, Rect) \
    do{\
        SetTextColor(m_MemoryDC, Color);\
        TCHAR * buf = new TCHAR[Buffer.size()+1]();\
        _tcscpy_s(buf, Buffer.size()+1, Buffer.c_str());\
        DrawTextEx(m_MemoryDC, buf, Buffer.size(), &Rect, Option, 0);\
        delete[] buf;\
    }while(false)

const int m_Margin = 25;
const int m_MinLineCounter = 15;

CLrcWinShower::CLrcWinShower():
m_LrcBuf(0), m_SigLineHeight(0),m_LineInterval(4),
m_MaxLenIdx(0),
m_KaraokeMode(false), m_LineChgMode(false), m_Gradient(false), m_AutoResizeMode(false),m_TopMostMode(false),
m_Alignment(0),
m_ScrollLrcTimer(NULL), m_GrandChangeTimer(NULL),
m_MemBitmap(NULL), m_BgBrush(NULL), m_LrcTextFont(NULL), 
m_ShowWinHandler(NULL), m_MemoryDC(NULL), m_WinDC(NULL)
{ 
     // Default color initialization
  	 m_UnFocusClr = RGB(84,142,165);  // Default color of the lrc lines except focus line
     m_FocusClr = RGB(255,0,0);       // Default color of the focus lrc line
	 m_BgClr = RGB(24,51,60);         // Default color of the focus lrc line 
     // Get system default font 
     GetObject(GetStockObject(SYSTEM_FONT), sizeof(LOGFONT), &m_TextLogFont); 
     // Create critial section
     InitializeCriticalSection(&m_ParaAccessLock);
}

CLrcWinShower::~CLrcWinShower()
{ 
     // Use block to let m_ParaAccessLock release before detroyed
     {
        // Parameters' access locker
	    Lock locker(&m_ParaAccessLock);    

	    CleanUp();
     }
     DeleteCriticalSection(&m_ParaAccessLock);
}

bool CLrcWinShower::ChangeFont(const LOGFONT* Font)
{
	 assert(Font != NULL);
     Lock locker(&m_ParaAccessLock);
        
	 HFONT PreFont = m_LrcTextFont;
	 try{
	       if( (m_LrcTextFont = CreateFontIndirect(Font)) == NULL )
			   throw TEXT("Fail to create font");
		   if( m_MemoryDC )
		   {
			   if( SelectObject(m_MemoryDC, m_LrcTextFont) == NULL )
			       throw TEXT("Fail to select font object into memory dc");
			   if( !ResizeWin() )
			       throw TEXT("Fail to resize window");
		   }
	       // Destroy the previous font	        
		   if( PreFont )
		       DeleteObject(PreFont);
           // Copy new logfont
           memcpy(&m_TextLogFont, Font, sizeof(LOGFONT));
		   return true;
	 }catch(const TCHAR * ){
	       if( m_LrcTextFont )
		       DeleteObject(m_LrcTextFont);
		   m_LrcTextFont = PreFont;
		   return false;
     }
}

bool CLrcWinShower::ChangeFont(const TCHAR * FontName, int PointSize)
{
	 HFONT TmpFont = NULL;
     int nHeight = -MulDiv(PointSize, GetDeviceCaps(m_WinDC, LOGPIXELSY), 72);
     if( (TmpFont = CreateFont(nHeight, 0,0,0,FW_SEMIBOLD,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH, FontName)) == FALSE )
         return false;
	 else {
	     LOGFONT Font;
		 GetObject(TmpFont, sizeof(Font), &Font);
         bool Result = ChangeFont(&Font);
		 DeleteObject(TmpFont);
		 return Result;
     }
}

bool CLrcWinShower::ChangeShowWnd(HWND Win)
{
     Lock locker(&m_ParaAccessLock);

     assert(Win != NULL);
     // If it is the same window, then just return
     if( m_ShowWinHandler  == Win )  return true;

     // If it hasn't start, then just return
     if( !m_ScrollLrcTimer ) 
     {
	     m_ShowWinHandler  = Win; 
	     return true;
     }

     HDC TmpWinDC = NULL;
     HDC TmpMemDC = NULL;
     HBITMAP TmpBitmap = NULL;

     try {
	        // Get window's DC. may be failure, must check it
            if( (TmpWinDC = GetDC(Win)) == NULL )
			    throw TEXT("Fail to get window's DC");

		    // Get memory DC
		    if( (TmpMemDC = CreateCompatibleDC(TmpWinDC)) == NULL )
			    throw TEXT("Fail to create memory DC");
     		 
		    // Create memory bitmap
		    RECT Child = {};
            GetClientRect(Win, &Child);
            if( (TmpBitmap = CreateCompatibleBitmap(TmpWinDC, Child.right - Child.left, Child.bottom - Child.top)) == FALSE ) 
			    throw TEXT("Fail to create memory bitmap");

		    // Get single line's height
		    tstring Line = (*m_LrcBuf)[0].m_Line;
            RECT  r = {};
            TCHAR * buf = new TCHAR[Line.size()+1]();
            _tcscpy_s(buf, Line.size()+1, Line.c_str());
            m_SigLineHeight = DrawTextEx(TmpMemDC, buf, Line.size(), &r, DT_CALCRECT, 0);
            delete[] buf;

		    // Select font & bitmap & text color & background color
		    SelectObject(TmpMemDC, m_LrcTextFont);
            SelectObject(TmpMemDC, TmpBitmap);
		    SetTextColor(TmpMemDC, m_UnFocusClr);
		    SetBkColor(TmpMemDC, m_BgClr);

		    // Release the previous gdi objects
		    if( m_WinDC )
		    {
			    ReleaseDC(m_ShowWinHandler, m_WinDC);
			    m_WinDC = NULL;
		    }
            
		    if( m_MemoryDC )
		    {
			    DeleteDC(m_MemoryDC);
			    m_MemoryDC = NULL;
		    }

		    if( m_MemBitmap )
		    {
			    DeleteObject(m_MemBitmap);
			    m_MemBitmap = NULL;
		    }
     		
            // Assgin the new handler's value
		    m_ShowWinHandler  = Win; 
	        m_WinDC = TmpWinDC;
            m_MemoryDC = TmpMemDC;
            m_MemBitmap = TmpBitmap;

		    m_DrawWidth = Child.right - Child.left;   // Window's width & height
		    m_DrawHeight = Child.bottom - Child.top;        
            m_FirstLineTextOutY = m_DrawHeight/2;     // Initial position where the focus line show( x & y )
            m_FocusLineLen = 0;                                 

            // Set the lrc show area rectangle
            m_ShowArea.top = m_ShowArea.left = 0;
            m_ShowArea.bottom = m_DrawHeight;
            m_ShowArea.right = m_DrawWidth;

		    return true;

     }catch(const TCHAR * ){
	        // Just do rollback
		    // Release  temporary gdi objects
		    if( TmpWinDC )
			    ReleaseDC(Win, TmpWinDC);
            
		    if( TmpMemDC )
			    DeleteDC(TmpMemDC);

		    if( TmpBitmap )
			    DeleteObject(TmpBitmap);

	        return false;
     }

     // According to the size of font and current window's size to decide wether resize it
     return ResizeWin();
}

bool CLrcWinShower::ChangeMainLrcBuf(const CLrcAnalyzer & Analyzer)
{
     Lock locker(&m_ParaAccessLock);

     m_MaxLenIdx = Analyzer.GetMaxLenIdx();
	 m_LrcBuf = Analyzer.GetLrcBuf();

	 // If it has been started then resize window if needed
	 if( m_ScrollLrcTimer ) 
	     return ResizeWin();
	 	  
     return true;
}

bool CLrcWinShower::ChangeBgClr(COLORREF Clr)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);    

     // If it is the same color, then just return
	 if( m_BgClr == Clr ) return true;

     // Select the new background color into memory dc
     if( m_MemoryDC )
   	     SetBkColor(m_MemoryDC, Clr);
		  
     // Create the new color brush
	 HBRUSH TmpBrush = m_BgBrush;
     if( (m_BgBrush = CreateSolidBrush(Clr)) == NULL )
	 {
   	      SetBkColor(m_MemoryDC, m_BgClr);
          m_BgBrush = TmpBrush;
          return false;
	 }
     
     // Delete old brush    
	 if( TmpBrush )
		 DeleteObject(TmpBrush);
     m_BgClr = Clr;

     return true;
}

bool CLrcWinShower::ChangeFocusLineClr(COLORREF  Clr)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);     

     m_FocusClr = Clr;
     return true;
}

bool CLrcWinShower::ChangeOtherLineClr(COLORREF  Clr)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);     

     m_UnFocusClr = Clr;
     return true;
}

bool CLrcWinShower::ChangeAlign(int Opt)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);   

     m_Alignment = Opt;
     return true;
}

bool CLrcWinShower::ChangeLineInterval(int Interval)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);   

     m_LineInterval = Interval;
     return true;
}

bool CLrcWinShower::ChangeGradientMode(bool OnOrOff)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);   

     m_LineChgMode = OnOrOff;
     return true;
}

bool CLrcWinShower::SetAutoResizeMode(bool Mode)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);    

	 // If it is in the same mode, then just return
	 if( m_AutoResizeMode == Mode ) return true;

     m_AutoResizeMode = Mode;
     // Auto resize if needed
	 return ResizeWin();
}

bool CLrcWinShower::SetTopMostMode(bool Mode)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);    
     m_TopMostMode = Mode;
     if( m_ShowWinHandler )
         SetWindowPos(m_ShowWinHandler, (Mode ? HWND_TOPMOST:HWND_NOTOPMOST),0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
     return true;
}


bool CLrcWinShower::ChangeKaraokeMode(bool Mode)
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);    

     m_KaraokeMode = Mode;
     return true;
}

bool CLrcWinShower::ResizeWin()
{
	 // Wether need to auto resize
	 if( !m_AutoResizeMode ) return true;
		 
	 // Check the m_LrcBuf & window handler
     if( !m_LrcBuf || m_LrcBuf->size() <= 0 || !m_ShowWinHandler )
         return true;

  	 // Get the maximum length lrc's drawing length
     tstring Line = (*m_LrcBuf)[m_MaxLenIdx].m_Line;
	 RECT  r = {};
     TCHAR * buf = new TCHAR[Line.size()+1]();
     _tcscpy_s(buf, Line.size()+1, Line.c_str());
     int SigLineHeight = DrawTextEx( m_MemoryDC, buf, Line.size(), &r, DT_CALCRECT, 0 );
     int MaxLength = r.right - r.left;
     delete[] buf;

	 // Check it wether we really need to resize
	 RECT ClientRect, WindowRect;
	 GetClientRect(m_ShowWinHandler, &ClientRect);
     GetWindowRect(m_ShowWinHandler, &WindowRect);
	 int ClientWidth = ClientRect.right - ClientRect.left;
	 int ClientHeight = ClientRect.bottom - ClientRect.top;
     int WindowWidth = WindowRect.right - WindowRect.left;
     int WindowHeight = WindowRect.bottom - WindowRect.top;
	 if( (MaxLength + m_Margin * 2) <= ClientWidth &&  
         ((SigLineHeight + m_LineInterval) * m_MinLineCounter) <= ClientHeight )
	     return true;

     // Change new size bitmap
 	 HBITMAP TmpBitmap = m_MemBitmap;
	 if( (m_MemBitmap = CreateCompatibleBitmap(m_WinDC, MaxLength + m_Margin * 2, (SigLineHeight + m_LineInterval) * m_MinLineCounter)) == FALSE )
     {
	     m_MemBitmap = TmpBitmap;
         return false;	
	 } else if( TmpBitmap )
         DeleteObject(TmpBitmap);
	 SelectObject(m_MemoryDC, m_MemBitmap);

	 // Get the lrc show's width & height
	 m_SigLineHeight = SigLineHeight;
     m_MaxLength = MaxLength;
     // Maximum length and margins on two sides
	 m_DrawWidth = m_MaxLength + m_Margin * 2;  
     // At least the window is able to accommodate 10 lines 
	 m_DrawHeight = (m_SigLineHeight + m_LineInterval) * m_MinLineCounter;   

	 // Resize the window to accommodate lrc lines
     SetWindowPos(m_ShowWinHandler, 
                 (m_TopMostMode ? HWND_TOPMOST:HWND_NOTOPMOST),0,0,
                  m_DrawWidth+WindowWidth-ClientWidth,
                  m_DrawHeight+WindowHeight-ClientHeight,
                  SWP_NOMOVE|SWP_SHOWWINDOW);

     //MoveWindow(m_ShowWinHandler, WindowRect.left, WindowRect.top, 
     //           m_DrawWidth+WindowWidth-ClientWidth, 
     //           m_DrawHeight+WindowHeight-ClientHeight, TRUE);
   
	 // Save the lrc show area rectangle
	 m_ShowArea.top = m_ShowArea.left = 0;
	 m_ShowArea.bottom = m_DrawHeight;
	 m_ShowArea.right = m_DrawWidth;

	 return true;
}

bool CLrcWinShower::GdiObjInit()
{
	 try{
	      // Check the m_LrcBuf & window handler
          if( !m_LrcBuf || m_LrcBuf->size() <= 0 || m_ShowWinHandler == NULL)
              return false;

          // Create window dc
          if( (m_WinDC = GetDC(m_ShowWinHandler)) == NULL )
              throw TEXT("Fail to create window DC");
          // Create memory DC
		  if( (m_MemoryDC = CreateCompatibleDC(m_WinDC)) == NULL )
		      throw TEXT("Fail to create memory DC");

          // Create lrc font
          if( !m_LrcTextFont )
          {
		      if( (m_LrcTextFont = CreateFontIndirect(&m_TextLogFont)) == FALSE )
		           throw TEXT("Fail to create font");
          }
		  SelectObject(m_MemoryDC, m_LrcTextFont);

		  // Get the lrc show's width & height
		  RECT Child;
		  GetClientRect(m_ShowWinHandler, &Child);
          // Showing window's width & height
	      m_DrawWidth = Child.right - Child.left;  
		  m_DrawHeight = Child.bottom - Child.top;
		  m_FirstLineTextOutY = m_DrawHeight/2;    // Initial position where the focus line show( x & y )
          m_FocusLineLen = 0;

          // Set the lrc show area rectangle
		  m_ShowArea.top = m_ShowArea.left = 0;
		  m_ShowArea.bottom = m_DrawHeight;
		  m_ShowArea.right = m_DrawWidth;

		  // Create memory bitmap
          if( (m_MemBitmap = CreateCompatibleBitmap(m_WinDC, m_DrawWidth, m_DrawHeight)) == FALSE ) 
		      throw TEXT("Fail to create memory bitmap");
          SelectObject(m_MemoryDC, m_MemBitmap);

		  // Creat the background brush
	      if( (m_BgBrush = CreateSolidBrush(m_BgClr)) == FALSE )
		      throw TEXT("Fail to create background brush");

		  // Set text color and background color
		  SetTextColor(m_MemoryDC, m_UnFocusClr);
		  SetBkColor(m_MemoryDC, m_BgClr);

		  // According to the size of font and current window's size to decide wether resize it
		  if( !ResizeWin() )
		      throw TEXT("Fail to resize window");

          // Set topmost
          if( m_TopMostMode )
              SetWindowPos(m_ShowWinHandler, (m_TopMostMode ? HWND_TOPMOST:HWND_NOTOPMOST),0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);

		  return true;

	 }catch(const TCHAR * ){
		  CleanUp();
		  return false;
	 }
}

bool CLrcWinShower::Start()
{
     // Parameters' locker
     Lock locker(&m_ParaAccessLock);

	 // If it has been started, then just do nothing and return
	 if( m_ScrollLrcTimer ) return true;

	 // Initialize the gdi objects;
	 if( GdiObjInit() == false )
		 return false;

	 // Reset all other parameters(light initialization)
     if( ResetHelper() == false )
	     return false;

     // Start the refresh timeout working thread
	 if( (m_ScrollLrcTimer = timeSetEvent(50, 0, (LPTIMECALLBACK)ScrollLrcTimeOut, (DWORD)this, TIME_PERIODIC)) == NULL )
		 return false;

	 return true;
}
   
bool CLrcWinShower::Reset()
{
     // Lock the scroller's parameters access at first
	 Lock locker(&m_ParaAccessLock);

     return ResetHelper();
}

bool CLrcWinShower::Stop()
{
     // Parameters' access locker
	 Lock locker(&m_ParaAccessLock);    
     // Clean up all gdi object
     CleanUp();
     // Clear main lrc buffer
     m_LrcBuf = NULL;
     return true;
}

void CLrcWinShower::ScrollLrcTimeOut(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
     UNREFERENCED_PARAMETER(dw2);
     UNREFERENCED_PARAMETER(dw1);
     UNREFERENCED_PARAMETER(msg);
     UNREFERENCED_PARAMETER(wTimerID);
     CLrcWinShower * Self = (CLrcWinShower*)dwUser;
     Self->ShowLrc();
}

void CLrcWinShower::GradientOffTimeOut(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
     UNREFERENCED_PARAMETER(dw2);
     UNREFERENCED_PARAMETER(dw1);
     UNREFERENCED_PARAMETER(msg);
     UNREFERENCED_PARAMETER(wTimerID);
     CLrcWinShower * Self = (CLrcWinShower*)dwUser;
     Self->TurnOffGradient();
}

void CLrcWinShower::AdjustLrcLineBuf(int UpperCount)
{
     // UpperCount must ben't zero
     assert(UpperCount >= 0);

     m_UpperLines.clear();
     m_UpperLineCount = 0;
     for( int i = 0; i < UpperCount; ++i, ++m_UpperLineCount )
          m_UpperLines += (*m_LrcBuf)[i].m_Line + _T("\n");
       
	 //  Make the line before focus line's color gradually changed
	 //  When the first time to change current focus line, we start the gradually changing clock if it is in gradual mode
	 if( m_FocusLine !=  (*m_LrcBuf)[UpperCount].m_Line )
	 {
         if( m_LineChgMode )
		 {
		     if( m_GrandChangeTimer ) 
			 {
			     timeKillEvent(m_GrandChangeTimer);
				 m_GrandChangeTimer = NULL;   
			 }
			 m_Gradient = true;
             m_GrandChangeTimer = timeSetEvent(350, 0, (LPTIMECALLBACK)GradientOffTimeOut, (DWORD)this, TIME_ONESHOT);
		 }

		 m_FocusLine = (*m_LrcBuf)[UpperCount].m_Line;
		 RECT  r = {};
		 TCHAR * buf = new TCHAR[m_FocusLine.size()+1]();
		 _tcscpy_s(buf, m_FocusLine.size()+1, m_FocusLine.c_str());
		 DrawTextEx( m_MemoryDC, buf, m_FocusLine.size(), &r, DT_CALCRECT, 0 );
		 m_FocusLineWidth = r.right - r.left;
		 delete[] buf;
	 }
 
     int Size = m_LrcBuf->size();
     m_LowerLines.clear();
     m_LowerLineCount = 0;
     for( int i = UpperCount+1; i < Size; ++i, ++m_LowerLineCount )
          m_LowerLines += (*m_LrcBuf)[i].m_Line + _T("\n");
} 

int CLrcWinShower::FindRange(long long Start)
{
     int Size = m_LrcBuf->size();
     for( int i = 0; i < Size; ++i )
          if( (*m_LrcBuf)[i].m_Time <= Start && Start < ((*m_LrcBuf)[i].m_Time+ (*m_LrcBuf)[i].m_Duration) )
              return i;
     return -1;
}

void CLrcWinShower::CalcEllapse(int RangeIdx)
{
     assert(RangeIdx < m_LrcBuf->size());

     long long Duration = (*m_LrcBuf)[RangeIdx].m_Duration;
     // Time slot(divides by height)
     m_Ellapse1 = Duration / (m_SigLineHeight + m_LineInterval);
     // Remainder
     m_Ellapse2 =  m_Ellapse1;
     // If it has a remainder
     if( Duration % (m_SigLineHeight + m_LineInterval) )
         m_Ellapse2 += Duration % (m_SigLineHeight + m_LineInterval);
}

int CLrcWinShower::FindTimeSlotRange(long long Start, int RangeIdx)
{
    if( RangeIdx >= m_LrcBuf->size() )
        return -1;
      
    // Calculate the ellapse
    CalcEllapse(RangeIdx);

    // Sepcify the first time slot
    long long StartTime =  (*m_LrcBuf)[RangeIdx].m_Time;
    if( StartTime <= Start && Start < StartTime + m_Ellapse2 )
        return 0;
    // Check it lays in which slot
    for( int i = 1; i < (m_SigLineHeight + m_LineInterval); ++i )
    {
         long long begin = StartTime + m_Ellapse2 + (i-1)*m_Ellapse1;
         if( begin <= Start && Start < begin + m_Ellapse1 )
             return i;
    }

    // Something must be wrong, impossible to run here
    return -2;
}

void CLrcWinShower::DoOnTimeScrollJob(long long Now)
{
     Lock locker(&m_ParaAccessLock);
     // Check the lrc main buffer at first
     if( !m_LrcBuf || m_LrcBuf->size() <= 0 )
         return;

     // Initialize the first line out put position' Y value
     m_FirstLineTextOutY = m_DrawHeight/2;
     // Get the first lien output y's value
     int RangeIdx = -1;
     if( (RangeIdx = FindRange(Now)) == -1 )
         return;
         
     // Adjust the 3 lrc buffers according to range index, caculate the 
     // value of m_SigLineHeight & m_FocusLineWidth
     AdjustLrcLineBuf(RangeIdx);

     //calc the first line's show position
     if( RangeIdx )
         m_FirstLineTextOutY -= RangeIdx  * (m_SigLineHeight + m_LineInterval);

     int SlotIdx = FindTimeSlotRange(Now, RangeIdx);
     if( SlotIdx >= 0 )
     {
         m_FirstLineTextOutY -= SlotIdx; 
         if( m_Alignment == DT_CENTER )
             m_FocusLineLen = (int)ceil((m_DrawWidth-m_FocusLineWidth)*0.5+m_FocusLineWidth*1.0/(m_SigLineHeight + m_LineInterval)*(SlotIdx+1));
         else if( m_Alignment == DT_LEFT )
             m_FocusLineLen = (int)ceil(m_FocusLineWidth*1.0/(m_SigLineHeight + m_LineInterval)*(SlotIdx+1));
         else
             m_FocusLineLen = (int)ceil((m_DrawWidth-m_FocusLineWidth)+m_FocusLineWidth*1.0/(m_SigLineHeight + m_LineInterval)*(SlotIdx+1));
     } else
         m_FocusLineLen = 0;

}

bool CLrcWinShower::ShowLrc()
{
     // Parameters' access locker
     Lock locker(&m_ParaAccessLock);     
     return TextToScreen();
}

void CLrcWinShower::TurnOffGradient()
{
     m_Gradient = false;
}

inline COLORREF GenerateGradualClr(COLORREF Clr)
{
     int R = GetRValue(Clr);
	 int G = GetGValue(Clr);
	 int B = GetBValue(Clr);
 
     R = R + 90 > 255 ? 255 : R + 90;
	 G = G + 90 > 255 ? 255 : G + 90;
	 B = B + 90 > 255 ? 255 : B + 90;

	 return RGB(R,G,B);
}


void CLrcWinShower::LineToScreen(tstring & Buffer, RECT  Rect, COLORREF Clr, int WhichPart)
{
	 tstring::size_type Idx = Buffer.find('\n', 0);
	 tstring::size_type Start = 0;
	 RECT Rectangle = Rect;
	 if( Idx == tstring::npos ) {            // Only one line
	     if( m_Gradient && WhichPart == 0 )  // If in the gradual mode and the current part is the upper one
			 Clr = GenerateGradualClr(m_FocusClr);
         // Gradually change the color of last line of upper parts
		 Text2Screen(Buffer, Alignment[m_Alignment]|DT_NOPREFIX, Clr, Rectangle); 
     } else {
	     while( Idx != tstring::npos )
		 {
		        tstring Sub = Buffer.substr(Start, Idx-Start+1);
				Rectangle.bottom = Rectangle.top + m_SigLineHeight + m_LineInterval;
				// If in the gradual mode , the current part is the upper one and it is the last line
				if( m_Gradient && WhichPart == 0 && Buffer.find('\n', Idx+1) == tstring::npos )
				    Clr = GenerateGradualClr(m_FocusClr);
				Text2Screen(Sub, Alignment[m_Alignment]|DT_NOPREFIX, Clr, Rectangle);
 
                // Find next position
				Start = Idx+1;
				Idx = Buffer.find('\n', Start);
				Rectangle.top = Rectangle.bottom;
		 }
	 }
}


bool CLrcWinShower::TextToScreen()
{
     // Check wether focus line buffer is empty
     if( m_FocusLine.empty() ) return false;

     // Fill the show area with the background color
     FillRect(m_MemoryDC, &m_ShowArea, m_BgBrush);

     RECT  Rectangle;
	 Rectangle.left   = 0;
	 Rectangle.right  = m_DrawWidth;
     Rectangle.top    = m_FirstLineTextOutY;
     Rectangle.bottom = Rectangle.top + m_UpperLineCount*(m_SigLineHeight + m_LineInterval);
        
     // Draw upper lines
     if( m_UpperLineCount ) 
		 LineToScreen(m_UpperLines, Rectangle, m_UnFocusClr, 0);
            
     // Draw the lower lines
     if( m_LowerLineCount )
     {
         Rectangle.top = Rectangle.bottom + m_SigLineHeight + m_LineInterval;
	     Rectangle.bottom = m_DrawHeight;
		 LineToScreen(m_LowerLines, Rectangle, m_UnFocusClr, 2);
     }

     // Draw focus line
     Rectangle.top = m_FirstLineTextOutY+ m_UpperLineCount*(m_SigLineHeight + m_LineInterval);
     Rectangle.bottom = Rectangle.top + m_SigLineHeight + m_LineInterval;
     if( m_KaraokeMode )  
     {
		 int margin = int((m_DrawWidth-m_FocusLineWidth)*0.5);
         HRGN PaintedPart, UnpaintedPart;
		 SetBkMode(m_MemoryDC, TRANSPARENT);
         // Calculate the two regions
         PaintedPart = CreateRectRgn(0, Rectangle.top, m_FocusLineLen, Rectangle.bottom);
         UnpaintedPart = CreateRectRgn(m_FocusLineLen, Rectangle.top, m_DrawWidth,  Rectangle.bottom);
         // Paint focus part
		 SelectClipRgn(m_MemoryDC, PaintedPart); 
         Text2Screen(m_FocusLine, Alignment[m_Alignment]|DT_NOPREFIX, m_FocusClr, Rectangle);
         // Paint unfocus part
         SelectClipRgn(m_MemoryDC, UnpaintedPart); 
         Text2Screen(m_FocusLine, Alignment[m_Alignment]|DT_NOPREFIX, m_UnFocusClr, Rectangle);
         // Remove the region
         SelectClipRgn(m_MemoryDC, NULL);

         DeleteObject(PaintedPart);
         DeleteObject(UnpaintedPart);
     } else 
         LineToScreen(m_FocusLine, Rectangle, m_FocusClr, 1);
     
     // Copy the painted lrc to window's DC        
     BitBlt(m_WinDC, 0, 0, m_DrawWidth, m_DrawHeight, m_MemoryDC, 0, 0, SRCCOPY);

	 return true;
}

void CLrcWinShower::CleanUp()
{         
     // Stop timers
     if( m_ScrollLrcTimer )
     {
         timeKillEvent(m_ScrollLrcTimer);
         m_ScrollLrcTimer = NULL;   
     }

	 if( m_GrandChangeTimer ) 
	 {
	     timeKillEvent(m_GrandChangeTimer);
		 m_GrandChangeTimer = NULL;   
	 }

     // Release gdi objects
     if( m_WinDC )
     {
         ReleaseDC(m_ShowWinHandler, m_WinDC);
         m_WinDC = NULL;
     }
        
     if( m_MemoryDC )
     {
         DeleteDC(m_MemoryDC);
         m_MemoryDC = NULL;
     }

     if( m_MemBitmap )
     {
         DeleteObject(m_MemBitmap);
         m_MemBitmap = NULL;
     }

     if( m_LrcTextFont )
     {
      	 DeleteObject(m_LrcTextFont);
         m_LrcTextFont = NULL;
     }

     if( m_BgBrush )
     {
      	 DeleteObject(m_BgBrush);
         m_BgBrush = NULL;
     }
}

bool CLrcWinShower::ResetHelper()
{
     // Check the m_LrcBuf is not null 
     if( !m_LrcBuf || m_LrcBuf->size() <= 0 )
         return false;

     // Init the first line output position
     m_FirstLineTextOutY = m_DrawHeight/2;  // Initial position where the focus line show
     m_FocusLineLen = 0;                    // For karaoke show mode

     // Init the lrc buffer
     // Just the focus line and get the height & width of single focus line of lrc
     m_FocusLine = (*m_LrcBuf)[0].m_Line;
	 RECT r = {};
     TCHAR * buf = new TCHAR[m_FocusLine.size()+1]();
     _tcscpy_s(buf, m_FocusLine.size()+1, m_FocusLine.c_str());
     m_SigLineHeight = DrawTextEx( m_MemoryDC, buf, m_FocusLine.size(), &r, DT_CALCRECT, 0 );
     m_FocusLineWidth = r.right - r.left;
     delete[] buf;
                
     //init the line counters
     m_UpperLineCount = 0;
     m_LowerLineCount = 0;
       
     // Add lines to half of the LineCount
	 // Calculate how many lines can be shown in the lrc dialog at most
     int LineCount = m_DrawHeight / (m_SigLineHeight + m_LineInterval);
     unsigned int Size = min(LineCount/2, m_LrcBuf->size());
     m_UpperLines.clear();
     m_LowerLines.clear();
     for( unsigned int i = 1; i < Size; ++i, ++m_LowerLineCount )
          m_LowerLines += (*m_LrcBuf)[i].m_Line + _T("\n");
       
	 return true;
}
